package com.gitee.sparta.common.helpers;

import com.gitee.sparta.common.SpartaConstant;
import com.gitee.sparta.common.SpartaLuaConstant;
import com.gitee.sparta.core.property.SpartaCoreProperties;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisException;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * lua 脚本 sha 工具
 *
 * @author zyred
 * @since v 0.0.1
 */
@Slf4j
public class ScriptShaHelper {

    private static final String NO_SCRIPT = "NOSCRIPT";


    /**
     * 系统启动阶段，将 script 缓存到 redis 服务器中，并且将缓存的摘要放入到系统内存中进行保存
     * 如果一旦 redis 服务器执行了 script flush 命令的时候，会导致脚本不存在，系统会报 RedisException：NOSCRIPT 错误
     * 所以只要检查到该异常，就重新加载服务脚本即可
     *
     * @param ex         错误信息
     * @param client     redisson 客户端
     * @param properties 核心配置文件
     */
    public static void reloadScriptSha(Exception ex, RedissonClient client, SpartaCoreProperties properties) {
        if (!(ex instanceof RedisException) && !ex.getMessage().contains(NO_SCRIPT)) {
            return;
        }
        String regeditLock = properties.getSystemName().concat(SpartaConstant.COLON).concat(SpartaConstant.REGEDIT_LOCK);
        RLock lock = client.getLock(regeditLock);
        try {
            // 等待 5ms， 等待超时了，说明别的服务已经完成了脚本的加载
            // 脚本的 sha 特性是无论多少次 load, 脚本的 sha 都是一样的，所以一个集群中，只需要一个服务参与 load
            // 所有服务持有的 sha 都能被使用
            int timeout = 5;
            if (lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
                RMap<String, String> shaTab = client.getMap(properties.getShaTable());
                shaTab.remove(SpartaConstant.SHA_TABLE);
                Map<String, String> scriptLoaded = SpartaLuaConstant.loadScript(client);
                shaTab.putAll(scriptLoaded);
                SpartaLuaConstant.parseLoadedSha(scriptLoaded);
            }
        } catch (InterruptedException e) {
            log.error("[reload script distributed lock] current thread interrupted : {}, " +
                    "\n exception : {}", Thread.currentThread().getName(), e.getMessage(), e);
        } finally {
            lock.unlock();
        }
        log.info("script reloaded success.");
    }

}
